#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>


#define MSG_SIZE       10
#define MSG_FILE       "./msgfile"

struct msgbuf {
    long mtype; /* 消息编号*/
    char mtext[MSG_SIZE]; /* 消息正文 */
};

void print_err(char *str) {
    perror(str);
    exit(1);
}

/**
 * 创建或获取消息ID
 */
int create_or_get_msgque() {
    /* 创建一个消息队列的专用文件, ftok会用到该文件 */
    int fd = open(MSG_FILE, O_RDWR | O_CREAT, 0664);
    if (fd == -1) print_err("open fail");

    /* 利用存在的文件路径名和8位int数, 计算出key值  */
    int key = ftok(MSG_FILE, 'a');
    if (key == -1) print_err("ftok fail");

    /* 利用key创建或者获取消息队列 */
    int msgid = msgget(key, 0664 | IPC_CREAT);
    if (msgid == -1) print_err("msgget fail");

    /* 关闭已打开文件 */
    close(fd);

    return msgid;
}

static int msgid = -1; /* 消息队列标识符 */

/** 程序通过信号终止时,删除消息队列 */
void signal_fun(int signo) {
    msgctl(msgid, IPC_RMID, NULL);
    
    /* 程序结束时删除用于消息队列创建的临时文件 */
    unlink(MSG_FILE);

    exit(-1);
}

/**
 * 消息队列使用示例
 * 程序通过shell终端启动
 * 消息编号通过第2个参数传入（第1个参数是程序路径），程序只接收启动时设置的消息编号；发送每个消息时，可以手动指定消息编号
 */
int main(int argc, char** argv){
    int ret;
    long recv_msgtype = 0; // 消息编号

    if (argc != 2) {
        print_err("./a.out recv_msgtype\n");
    }

    recv_msgtype = atol(argv[1]);

    msgid = create_or_get_msgque();

    ret = fork();
    if (ret < 0) {
        print_err("fork fail");
    }
    else if (ret == 0) { // 子进程接收消息
        struct msgbuf msg_buf = {0};
        int ret = 0;
       
        while (1) {
            bzero(&msg_buf, sizeof msg_buf);
            // 接收指定消息编号的消息
            // 阻塞接收
            ret =  msgrcv(msgid, &msg_buf, MSG_SIZE, recv_msgtype, 0);
            if (ret > 0) printf("recv: %s\n", msg_buf.mtext);
            else perror("recv error\n");

        }
    }
    else { // 父进程发送消息
        struct msgbuf msg_buf = {0};

        signal(SIGINT, signal_fun); // 捕获SIG_INT信号(Ctrl + C触发)

        while (1) {
            bzero(&msg_buf, sizeof msg_buf);
            /* 封装消息包 */
            scanf("%s", msg_buf.mtext);
            printf("input snd_msgtype:\n");
            scanf("%ld", &msg_buf.mtype);

            /* 发送消息包 */
            msgsnd(msgid, &msg_buf, MSG_SIZE, 0);
        }
    }

    return 0;
}

